/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#include "cmFastbuildTargetGenerator.h"

#include <algorithm>
#include <cstddef>
#include <unordered_map>
#include <unordered_set>

#include <cm/memory>
#include <cm/optional>

#include "cmCryptoHash.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandGenerator.h"
#include "cmCustomCommandLines.h"
#include "cmFastbuildNormalTargetGenerator.h"
#include "cmFastbuildUtilityTargetGenerator.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalCommonGenerator.h"
#include "cmGlobalFastbuildGenerator.h"
#include "cmList.h"
#include "cmListFileCache.h"
#include "cmLocalCommonGenerator.h"
#include "cmLocalFastbuildGenerator.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmOSXBundleGenerator.h"
#include "cmOutputConverter.h"
#include "cmRulePlaceholderExpander.h"
#include "cmSourceFile.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"

#define FASTBUILD_DOLLAR_TAG "FASTBUILD_DOLLAR_TAG"

constexpr auto FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT =
  "CMAKE_FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT";
constexpr auto FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC =
  "CMAKE_FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC";

cmFastbuildTargetGenerator* cmFastbuildTargetGenerator::New(
  cmGeneratorTarget* target, std::string config)
{
  switch (target->GetType()) {
    case cmStateEnums::EXECUTABLE:
    case cmStateEnums::SHARED_LIBRARY:
    case cmStateEnums::STATIC_LIBRARY:
    case cmStateEnums::MODULE_LIBRARY:
    case cmStateEnums::OBJECT_LIBRARY:
      return new cmFastbuildNormalTargetGenerator(target, std::move(config));

    case cmStateEnums::UTILITY:
    case cmStateEnums::GLOBAL_TARGET:
    case cmStateEnums::INTERFACE_LIBRARY:
      return new cmFastbuildUtilityTargetGenerator(target, std::move(config));

    default:
      return nullptr;
  }
}

cmFastbuildTargetGenerator::cmFastbuildTargetGenerator(
  cmGeneratorTarget* target, std::string configParam)
  : cmCommonTargetGenerator(target)
  , LocalGenerator(
      static_cast<cmLocalFastbuildGenerator*>(target->GetLocalGenerator()))
  , TargetDirectDependencies(
      this->GlobalCommonGenerator->GetTargetDirectDepends(GeneratorTarget))
  , Config(std::move(configParam))
{
  this->MacOSXContentGenerator =
    cm::make_unique<MacOSXContentGeneratorType>(this, Config);
}

void cmFastbuildTargetGenerator::LogMessage(std::string const& m) const
{
  this->GetGlobalGenerator()->LogMessage(m);
}

std::string cmFastbuildTargetGenerator::GetUtilityAliasFromBuildStep(
  FastbuildBuildStep step) const
{
  if (step == FastbuildBuildStep::PRE_BUILD) {
    return GetTargetName() + FASTBUILD_PRE_BUILD_ALIAS_POSTFIX;
  }
  if (step == FastbuildBuildStep::PRE_LINK) {
    return GetTargetName() + FASTBUILD_PRE_LINK_ALIAS_POSTFIX;
  }
  if (step == FastbuildBuildStep::POST_BUILD) {
    return GetTargetName() + FASTBUILD_POST_BUILD_ALIAS_POSTFIX;
  }
  return GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;
}

void cmFastbuildTargetGenerator::MacOSXContentGeneratorType::operator()(
  cmSourceFile const& source, char const* pkgloc,
  std::string const& configName)
{
  // Skip OS X content when not building a Framework or Bundle.
  if (!this->Generator->GetGeneratorTarget()->IsBundleOnApple()) {
    return;
  }

  // Get the input file location.
  std::string input = source.GetFullPath();
  input = this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(input);

  // Get the output file location.
  std::string output =
    this->Generator->OSXBundleGenerator->InitMacOSXContentDirectory(
      pkgloc, configName);

  output += "/";
  output += cmSystemTools::GetFilenameName(input);
  output =
    this->Generator->GetGlobalGenerator()->ConvertToFastbuildPath(output);

  FastbuildCopyNode node;
  node.Name = "Copy_" + output;
  node.Source = std::move(input);
  if (cmSystemTools::FileIsDirectory(node.Source)) {
    node.CopyDir = true;
  }
  node.Dest = std::move(output);
  // Just in case if "from" is generated by some custom command.
  // Tested in "BundleTest" test.
  node.PreBuildDependencies =
    this->Generator->GetTargetName() + FASTBUILD_CUSTOM_COMMAND_ALIAS_POSTFIX;

  this->Generator->CopyNodes.emplace_back(std::move(node));
}

std::string cmFastbuildTargetGenerator::GetCustomCommandTargetName(
  cmCustomCommand const& cc, FastbuildBuildStep step) const
{
  std::string const extra = this->Makefile->GetCurrentBinaryDirectory();
  std::string targetName = "cc";

  std::string extras = extra;

  // Compute hash based on commands & args & output.
  for (cmCustomCommandLine const& commandLine : cc.GetCommandLines()) {
    extras += cmJoin(commandLine, "");
  }
  for (std::string const& output : cc.GetOutputs()) {
    extras += output;
  }

  extras += std::to_string(static_cast<int>(step));

  cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
  targetName += "-" + hash.HashString(extras).substr(0, 7);

  return targetName;
}

std::vector<std::string> cmFastbuildTargetGenerator::GetInputFiles(
  cmCustomCommandGenerator const& ccg, FastbuildBuildStep step) const
{
  std::vector<std::string> result;
  auto const& cc = ccg.GetCC();
  LogMessage("CC Name: " + GetCustomCommandTargetName(cc, step));
  for (std::string const& dep : ccg.GetDepends()) {
    LogMessage("Custom command dep: " + dep);
    // Tested in EmptyDepends test.
    std::string realDep;
    if (this->LocalCommonGenerator->GetRealDependency(dep, Config, realDep)) {
      auto list = cmList{ cmGeneratorExpression::Evaluate(
        this->ConvertToFastbuildPath(realDep), this->LocalGenerator, Config) };
      if (!realDep.empty()) {
        LogMessage("Custom command real dep: " + realDep);
        for (auto const& item : list) {
          result.emplace_back(item);
        }
      }
    }
  }

  if (cc.HasMainDependency()) {
    LogMessage("CC main dep: " + cc.GetMainDependency());
  }
  LogMessage("cc Target: " + cc.GetTarget());
  for (auto const& impDep : cc.GetImplicitDepends()) {
    LogMessage("CC imp dep: " + impDep.first + ", " + impDep.second);
  }
  for (std::string const& dep : cc.GetOutputs()) {
    LogMessage("Custom command output: " + this->ConvertToFastbuildPath(dep));
  }
  for (std::string const& dep : cc.GetByproducts()) {
    LogMessage("Custom command byproducts: " +
               this->ConvertToFastbuildPath(dep));
  }
  return result;
}

void cmFastbuildTargetGenerator::WriteScriptProlog(cmsys::ofstream& file) const
{
#ifdef _WIN32
  file << "@echo off\n";
#else
  file << "set -e\n\n";
#endif
}
void cmFastbuildTargetGenerator::WriteScriptEpilog(cmsys::ofstream& file) const
{
  (void)file;
#ifdef _WIN32
  file << "goto :EOF\n\n"
          ":ABORT\n"
          "set ERROR_CODE=%ERRORLEVEL%\n"
          "echo Batch file failed at line %FAIL_LINE% "
          "with errorcode %ERRORLEVEL%\n"
          "exit /b %ERROR_CODE%";
#endif
}

std::string cmFastbuildTargetGenerator::GetScriptWorkingDir(
  cmCustomCommandGenerator const& ccg) const
{
  std::string workingDirectory = ccg.GetWorkingDirectory();
  if (workingDirectory.empty()) {
    return this->LocalCommonGenerator->GetCurrentBinaryDirectory();
  }
  return workingDirectory;
}

std::string cmFastbuildTargetGenerator::GetScriptFilename(
  std::string const& utilityTargetName) const
{
  std::string scriptFileName = Makefile->GetCurrentBinaryDirectory();
  scriptFileName += "/CMakeFiles/";
  scriptFileName += utilityTargetName;
  scriptFileName += FASTBUILD_SCRIPT_FILE_EXTENSION;
  return scriptFileName;
}

void cmFastbuildTargetGenerator::AddCommentPrinting(
  std::vector<std::string>& cmdLines,
  cmCustomCommandGenerator const& ccg) const
{
  std::string cmakeCommand = this->GetLocalGenerator()->ConvertToOutputFormat(
    cmSystemTools::GetCMakeCommand(), cmOutputConverter::SHELL);
  auto const comment = ccg.GetComment();
  if (comment) {
    // Comment printing should be first. Tested in
    // RunCMake.ExternalProject:EnvVars-build test.
    cmdLines.insert(
      cmdLines.begin(),
      cmakeCommand.append(" -E echo ")
        .append(LocalGenerator->EscapeForShell(cmGeneratorExpression::Evaluate(
          *comment, this->LocalGenerator, Config))));
  }
}

std::string cmFastbuildTargetGenerator::GetCdCommand(
  cmCustomCommandGenerator const& ccg) const
{
  return cmStrCat(FASTBUILD_SCRIPT_CD,
                  this->LocalGenerator->ConvertToOutputFormat(
                    GetScriptWorkingDir(ccg), cmOutputConverter::SHELL));
}

void cmFastbuildTargetGenerator::WriteCmdsToFile(
  cmsys::ofstream& file, std::vector<std::string> const& cmds) const
{
#ifdef _WIN32
  int line = 1;
  for (auto cmd : cmds) {
    // On Windows batch, '%' is a special character that needs to be
    // doubled to be escaped
    cmSystemTools::ReplaceString(cmd, "%", "%%");
    file << cmd << " || (set FAIL_LINE=" << ++line << "& goto :ABORT)" << '\n';
#else
  for (auto const& cmd : cmds) {
    file << cmd << '\n';
#endif
  }
}

void cmFastbuildTargetGenerator::AddOutput(cmCustomCommandGenerator const& ccg,
                                           FastbuildExecNode& exec)
{
  std::string dummyOutput = cmSystemTools::JoinPath(
    { LocalCommonGenerator->GetMakefile()->GetHomeOutputDirectory(),
      "/_fbuild_dummy" });
  this->GetGlobalGenerator()->AllFoldersToClean.insert(dummyOutput);

  dummyOutput.append("/").append(exec.Name).append(
    FASTBUILD_DUMMY_OUTPUT_EXTENSION);

  std::vector<std::string> const& outputs = ccg.GetOutputs();
  std::vector<std::string> const& byproducts = ccg.GetByproducts();

  exec.OutputsAlias.Name = exec.Name + FASTBUILD_OUTPUTS_ALIAS_POSTFIX;
  // If CC doesn't have any output - we should always run it.
  // Tested in "RunCMake.CMakePresetsBuild" test.
  bool hasAnyNonSymbolicOutput = false;

  bool const trackByproducts =
    this->Makefile->IsDefinitionSet(FASTBUILD_TRACK_BYPRODUCTS_AS_OUTPUT);

  auto const isSymbolic = [this](std::string const& file) {
    cmSourceFile* sf = this->Makefile->GetSource(file);
    if (sf && sf->GetPropertyAsBool("SYMBOLIC")) {
      LogMessage("Skipping symbolic file: " + file);
      return true;
    }
    return false;
  };

  for (std::string const& output : outputs) {
    // Tested in "RunCMake.BuildDepends".
    if (isSymbolic(output)) {
      continue;
    }
    hasAnyNonSymbolicOutput = true;
    std::string const outputPath = this->ConvertToFastbuildPath(output);
    LogMessage("CC's output: " + outputPath);
    exec.OutputsAlias.PreBuildDependencies.emplace(outputPath);
    // Ensure output path exists. For some reason, "CMake -E touch" fails with
    // "cmake -E touch: failed to update "...
    cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(outputPath));
    this->GetGlobalGenerator()->AddFileToClean(outputPath);
  }

  exec.ByproductsAlias.Name = exec.Name + FASTBUILD_BYPRODUCTS_ALIAS_POSTFIX;
  for (std::string const& byproduct : byproducts) {
    if (trackByproducts) {
      hasAnyNonSymbolicOutput = true;
    }
    std::string const byproductPath = this->ConvertToFastbuildPath(byproduct);
    exec.ByproductsAlias.PreBuildDependencies.emplace(byproductPath);
    this->GetGlobalGenerator()->AddFileToClean(byproductPath);
  }

  auto const addDummyOutput = [&] {
    // So that the dummy file is always created.
    exec.ExecUseStdOutAsOutput = true;
    exec.ExecOutput = this->ConvertToFastbuildPath(dummyOutput);
    for (auto const& output : exec.OutputsAlias.PreBuildDependencies) {
      OutputsToReplace[output.Name] = exec.ExecOutput;
      LogMessage("Adding replace from " + output.Name + " to " +
                 exec.ExecOutput);
    }
  };

  // We don't have any output that is expected to appear on disk -> run always.
  // Tested in "RunCMake.ExternalProject":BUILD_ALWAYS
  if (!hasAnyNonSymbolicOutput) {
    exec.ExecAlways = true;
    addDummyOutput();
    return;
  }

  if (!exec.OutputsAlias.PreBuildDependencies.empty()) {
    exec.ExecOutput = this->ConvertToFastbuildPath(
      exec.OutputsAlias.PreBuildDependencies.begin()->Name);
  } else {
    exec.ExecOutput = this->ConvertToFastbuildPath(
      exec.ByproductsAlias.PreBuildDependencies.begin()->Name);
  }

  // Optionally add the "deps-check" Exec if we have more than 1 OUTPUT, but
  // allow user to opt out.
  if (exec.OutputsAlias.PreBuildDependencies.size() > 1 &&
      !this->Makefile->IsDefinitionSet(
        FASTBUILD_DISABLE_OUTPUT_PRECHECK_EXEC)) {
    exec.NeedsDepsCheckExec = true;
  }
}

void cmFastbuildTargetGenerator::AddExecArguments(
  FastbuildExecNode& exec, std::string const& scriptFilename) const
{
  exec.ExecArguments = FASTBUILD_SCRIPT_FILE_ARG;
  exec.ExecArguments +=
    cmGlobalFastbuildGenerator::QuoteIfHasSpaces(scriptFilename);

  exec.ScriptFile = scriptFilename;
  exec.ExecExecutable =
    cmGlobalFastbuildGenerator::GetExternalShellExecutable();
}

std::vector<std::string> cmFastbuildTargetGenerator::GetDepends(
  cmCustomCommandGenerator const& ccg) const
{
  std::vector<std::string> res;
  for (auto dep : ccg.GetDepends()) {
    LogMessage("Dep: " + dep);
    auto orig = dep;
    if (this->LocalCommonGenerator->GetRealDependency(dep, Config, dep)) {
      LogMessage("Real dep: " + dep);
    }
    dep = this->ConvertToFastbuildPath(dep);
    LogMessage("Real dep converted: " + dep);

    auto const targetInfo = this->LocalGenerator->GetSourcesWithOutput(dep);
    if (targetInfo.Target) {
      LogMessage("dep: " + dep + ", target: " + targetInfo.Target->GetName());
      auto const& target = targetInfo.Target;
      auto const processCCs = [this, &res,
                               dep](std::vector<cmCustomCommand> const& ccs,
                                    FastbuildBuildStep step) {
        for (auto const& cc : ccs) {
          for (auto const& output : cc.GetOutputs()) {
            LogMessage("dep: " + dep + ", post output: " +
                       this->ConvertToFastbuildPath(output));
            if (this->ConvertToFastbuildPath(output) == dep) {
              auto ccName = this->GetCustomCommandTargetName(cc, step);
              LogMessage("Additional CC dep from target: " + ccName);
              res.emplace_back(std::move(ccName));
            }
          }
          for (auto const& byproduct : cc.GetByproducts()) {
            LogMessage("dep: " + dep + ", post byproduct: " +
                       this->ConvertToFastbuildPath(byproduct));
            if (this->ConvertToFastbuildPath(byproduct) == dep) {
              auto ccName = this->GetCustomCommandTargetName(cc, step);
              LogMessage("Additional CC dep from target: " + ccName);
              res.emplace_back(std::move(ccName));
            }
          }
        }
      };
      processCCs(target->GetPreBuildCommands(), FastbuildBuildStep::PRE_BUILD);
      processCCs(target->GetPreLinkCommands(), FastbuildBuildStep::PRE_LINK);
      processCCs(target->GetPostBuildCommands(),
                 FastbuildBuildStep::POST_BUILD);
      continue;
    }
    if (!targetInfo.Source) {
      LogMessage("dep: " + dep + ", no source, byproduct: " +
                 std::to_string(targetInfo.SourceIsByproduct));
      // Tested in "OutDir" test.
      res.emplace_back(std::move(orig));
      continue;
    }
    if (!targetInfo.Source->GetCustomCommand()) {
      LogMessage("dep: " + dep + ", no GetCustomCommand");
      continue;
    }
    if (targetInfo.Source && targetInfo.Source->GetCustomCommand()) {
      auto ccName = this->GetCustomCommandTargetName(
        *targetInfo.Source->GetCustomCommand(), FastbuildBuildStep::REST);
      LogMessage("Additional CC dep: " + ccName);
      res.emplace_back(std::move(ccName));
    }
  }
  return res;
}

void cmFastbuildTargetGenerator::ReplaceProblematicMakeVars(
  std::string& command) const
{
  // TODO: fix problematic global targets.  For now, search and replace the
  // makefile vars.
  cmSystemTools::ReplaceString(
    command, "$(CMAKE_SOURCE_DIR)",
    this->LocalGenerator->ConvertToOutputFormat(
      this->LocalGenerator->GetSourceDirectory(), cmOutputConverter::SHELL));
  cmSystemTools::ReplaceString(
    command, "$(CMAKE_BINARY_DIR)",
    this->LocalGenerator->ConvertToOutputFormat(
      this->LocalGenerator->GetBinaryDirectory(), cmOutputConverter::SHELL));
  cmSystemTools::ReplaceString(command, "$(ARGS)", "");
}

FastbuildExecNode cmFastbuildTargetGenerator::GetAppleTextStubCommand() const
{
  FastbuildExecNode res;
  if (!this->GeneratorTarget->IsApple() ||
      !this->GeneratorTarget->HasImportLibrary(Config)) {
    return res;
  }

  auto const names = DetectOutput();
  std::string const outpathImp =
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
      Config, cmStateEnums::ImportLibraryArtifact));

  std::string const binPath =
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetDirectory(
      Config, cmStateEnums::RuntimeBinaryArtifact));

  cmSystemTools::MakeDirectory(outpathImp);

  std::string rule = this->LocalGenerator->GetMakefile()->GetSafeDefinition(
    "CMAKE_CREATE_TEXT_STUBS");
  LogMessage("CMAKE_CREATE_TEXT_STUBS:" + rule);

  auto rulePlaceholderExpander =
    this->GetLocalGenerator()->CreateRulePlaceholderExpander();

  cmRulePlaceholderExpander::RuleVariables vars;
  res.ExecOutput = cmStrCat(outpathImp, '/', names.ImportReal);
  res.ExecInput = { cmStrCat(binPath, '/', names.SharedObject) };

  vars.Target = res.ExecInput[0].c_str();
  rulePlaceholderExpander->SetTargetImpLib(res.ExecOutput);
  rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(), rule,
                                               vars);

  LogMessage("CMAKE_CREATE_TEXT_STUBS expanded:" + rule);
  std::string executable;
  std::string args;
  if (!cmSystemTools::SplitProgramFromArgs(rule, executable, args)) {
    cmSystemTools::Error("Failed to split program from args: " + rule);
    return res;
  }

  res.Name = "create_" + names.ImportOutput + "_text_stub";
  res.ExecExecutable = std::move(executable);
  res.ExecArguments = std::move(args);
  res.ExecWorkingDir = this->LocalCommonGenerator->GetCurrentBinaryDirectory();

  // Wait for the build.
  res.PreBuildDependencies.emplace(this->GetTargetName());
  return res;
}
FastbuildExecNode cmFastbuildTargetGenerator::GetDepsCheckExec(
  FastbuildExecNode const& depender)
{
  FastbuildExecNode exec;
  exec.Name = depender.Name + "-check-depends";
  exec.ExecAlways = true;
  exec.ExecUseStdOutAsOutput = true;
  exec.ExecOutput = depender.ExecOutput + ".deps-checker";
  exec.ExecExecutable = cmSystemTools::GetCMakeCommand();
  exec.ExecArguments += "-E cmake_fastbuild_check_depends ";
  exec.ExecArguments += depender.ExecOutput + " ";
  char const* sep = "";
  for (auto const& dep : depender.OutputsAlias.PreBuildDependencies) {
    exec.ExecArguments += sep;
    exec.ExecArguments += dep.Name;
    sep = " ";
  }
  for (auto const& dep : depender.ByproductsAlias.PreBuildDependencies) {
    exec.ExecArguments += sep;
    exec.ExecArguments += dep.Name;
    sep = " ";
  }
  return exec;
}

FastbuildExecNodes cmFastbuildTargetGenerator::GenerateCommands(
  FastbuildBuildStep buildStep)
{
  FastbuildExecNodes execs;
  execs.Alias.Name = GetUtilityAliasFromBuildStep(buildStep);

  std::vector<cmCustomCommand> commands;
  if (buildStep == FastbuildBuildStep::PRE_BUILD) {
    commands = GeneratorTarget->GetPreBuildCommands();
    LogMessage("STEP: PRE_BUILD");
  } else if (buildStep == FastbuildBuildStep::PRE_LINK) {
    commands = GeneratorTarget->GetPreLinkCommands();
    LogMessage("STEP: PRE_LINK");
  } else if (buildStep == FastbuildBuildStep::POST_BUILD) {
    commands = GeneratorTarget->GetPostBuildCommands();
    LogMessage("STEP: POST_BUILD");
  } else {
    LogMessage("STEP: ALL CUSTOM COMMANDS");
    std::vector<cmSourceFile const*> customCommands;
    GeneratorTarget->GetCustomCommands(customCommands, Config);
    for (cmSourceFile const* source : customCommands) {
      cmCustomCommand const* cmd = source->GetCustomCommand();
      if (!cmd->GetCommandLines().empty()) {
        commands.emplace_back(*cmd);
      }
    }
  }
  LogMessage(cmStrCat("Number of custom commands: ", commands.size()));
  for (cmCustomCommand const& customCommand : commands) {
    cmCustomCommandGenerator ccg(customCommand, Config, LocalCommonGenerator);
    std::string launcher = this->MakeCustomLauncher(ccg);

    std::string const execName =
      GetCustomCommandTargetName(customCommand, buildStep);

    std::vector<std::string> cmdLines;
    if (ccg.GetNumberOfCommands() > 0) {
      cmdLines.push_back(GetCdCommand(ccg));
    }

    // Since we are not using FASTBuild Exec nodes natively, we need to
    // have shell specific escape.
    this->LocalGenerator->GetState()->SetFastbuildMake(false);
    // To avoid replacing $ with $$ in the command line.
    this->LocalGenerator->SetLinkScriptShell(true);
    for (unsigned j = 0; j != ccg.GetNumberOfCommands(); ++j) {
      std::string const command = ccg.GetCommand(j);
      // Tested in "CustomCommand" ("empty_command") test.
      if (!command.empty()) {

        cmdLines.emplace_back(launcher +
                              this->LocalGenerator->ConvertToOutputFormat(
                                command, cmOutputConverter::SHELL));

        std::string& cmd = cmdLines.back();
        ccg.AppendArguments(j, cmd);
        ReplaceProblematicMakeVars(cmd);
        LogMessage("cmCustomCommandLine: " + cmd);
      }
    }
    if (cmdLines.empty()) {
      return {};
    }
    this->LocalGenerator->GetState()->SetFastbuildMake(true);

    FastbuildExecNode execNode;
    execNode.Name = execName;
    std::vector<std::string> const inputFiles = GetInputFiles(ccg, buildStep);
    for (std::string const& file : inputFiles) {
      LogMessage("Input file: " + file);
    }
    for (auto const& util : ccg.GetUtilities()) {
      auto const& utilTargetName = util.Value.first;
      LogMessage("Util: " + utilTargetName +
                 ", cross: " + std::to_string(util.Value.second));
      auto* const target = this->Makefile->FindTargetToUse(utilTargetName);

      if (target && target->IsImported()) {
        std::string importedLoc =
          this->ConvertToFastbuildPath(target->ImportedGetFullPath(
            Config, cmStateEnums::ArtifactType::RuntimeBinaryArtifact));
        if (importedLoc.empty()) {
          importedLoc =
            this->ConvertToFastbuildPath(target->ImportedGetFullPath(
              Config, cmStateEnums::ArtifactType::ImportLibraryArtifact));
        }
        LogMessage("adding file level dep on imporated target: " +
                   importedLoc);
        execNode.PreBuildDependencies.emplace(std::move(importedLoc));
        continue;
      }
      // This CC uses some executable produced by another target. Add explicit
      // dep. Tested in "CustomCommand" test.
      if (util.Value.second) {
        if (utilTargetName != customCommand.GetTarget()) {
          LogMessage("Adding util dep: " + utilTargetName);
          execNode.PreBuildDependencies.emplace(utilTargetName);
        }
      }
    }

    execNode.ExecInput = inputFiles;
    execs.Alias.PreBuildDependencies.emplace(execNode.Name);

    LogMessage(cmStrCat("cmdLines size ", cmdLines.size()));

    if (!cmdLines.empty()) {
      std::string const scriptFileName = GetScriptFilename(execName);
      cmsys::ofstream scriptFile(scriptFileName.c_str());

      AddOutput(ccg, execNode);
      AddExecArguments(execNode, scriptFileName);
      AddCommentPrinting(cmdLines, ccg);

      WriteScriptProlog(scriptFile);
      WriteCmdsToFile(scriptFile, cmdLines);
      WriteScriptEpilog(scriptFile);

      execNode.ExecWorkingDir = GetScriptWorkingDir(ccg);
    }

    // Tested in "ObjectLibrary / complexOneConfig" tests.
    for (std::string& additionalDep : GetDepends(ccg)) {
      if (additionalDep != execName) {
        LogMessage("Adding additional dep: " + additionalDep);
        execNode.PreBuildDependencies.emplace(std::move(additionalDep));
      }
    }

    if (buildStep == FastbuildBuildStep::POST_BUILD) {
      execNode.PreBuildDependencies.emplace(GetTargetName() +
                                            FASTBUILD_BUILD_ALIAS_POSTFIX);
      // Execute POST_BUILD in order in which they are declared.
      // Tested in "complex" test.
      for (auto& exec : execs.Nodes) {
        execNode.PreBuildDependencies.emplace(exec.Name);
      }
    }
    for (auto const& out : execNode.OutputsAlias.PreBuildDependencies) {
      LogMessage("Adding replace from " + out.Name + " to " + execName);
      OutputToExecName[out.Name] = execName;
    }
    execs.Nodes.emplace_back(std::move(execNode));
  }
  for (auto& exec : execs.Nodes) {
    for (auto& inputFile : exec.ExecInput) {
      auto const iter = OutputsToReplace.find(inputFile);
      if (iter != OutputsToReplace.end()) {
        LogMessage("Replacing input: " + inputFile + " with " + iter->second);
        inputFile = iter->second;
      }
      auto const depIter = std::find_if(
        exec.PreBuildDependencies.begin(), exec.PreBuildDependencies.end(),
        [this](FastbuildTargetDep const& dep) {
          return !OutputToExecName[dep.Name].empty();
        });
      if (depIter != exec.PreBuildDependencies.end()) {
        LogMessage("Replacing dep " + depIter->Name + " with " +
                   OutputToExecName[depIter->Name]);
        exec.PreBuildDependencies.emplace(OutputToExecName[depIter->Name]);
        exec.PreBuildDependencies.erase(depIter);
      }
    }
    if (exec.NeedsDepsCheckExec) {
      auto depsCheckExec = GetDepsCheckExec(exec);
      LogMessage("Adding deps check Exec: " + depsCheckExec.Name);
      exec.PreBuildDependencies.emplace(depsCheckExec.Name);
      this->GetGlobalGenerator()->AddTarget(std::move(depsCheckExec));
    }
  }
  return execs;
}

std::string cmFastbuildTargetGenerator::MakeCustomLauncher(
  cmCustomCommandGenerator const& ccg)
{
  // Copied from cmLocalNinjaGenerator::MakeCustomLauncher.
  cmValue property_value = this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");

  if (!cmNonempty(property_value)) {
    return std::string();
  }

  // Expand rule variables referenced in the given launcher command.
  cmRulePlaceholderExpander::RuleVariables vars;

  std::string output;
  std::vector<std::string> const& outputs = ccg.GetOutputs();
  for (size_t i = 0; i < outputs.size(); ++i) {
    output =
      cmStrCat(output,
               this->LocalGenerator->ConvertToOutputFormat(
                 ccg.GetWorkingDirectory().empty()
                   ? this->LocalGenerator->MaybeRelativeToCurBinDir(outputs[i])
                   : outputs[i],
                 cmOutputConverter::SHELL));
    if (i != outputs.size() - 1) {
      output = cmStrCat(output, ',');
    }
  }
  vars.Output = output.c_str();
  vars.Role = ccg.GetCC().GetRole().c_str();

  auto rulePlaceholderExpander =
    this->LocalGenerator->CreateRulePlaceholderExpander();

  std::string launcher = *property_value;
  rulePlaceholderExpander->ExpandRuleVariables(this->LocalGenerator, launcher,
                                               vars);
  if (!launcher.empty()) {
    launcher += " ";
  }

  LogMessage("CC Launcher: " + launcher);
  return launcher;
}

std::string cmFastbuildTargetGenerator::GetTargetName() const
{
  if (this->GeneratorTarget->GetType() == cmStateEnums::GLOBAL_TARGET) {
    return this->GetGlobalGenerator()->GetTargetName(GeneratorTarget);
  }
  return this->GeneratorTarget->GetName();
}

cmGeneratorTarget::Names cmFastbuildTargetGenerator::DetectOutput() const
{
  if (GeneratorTarget->GetType() == cmStateEnums::EXECUTABLE) {
    return GeneratorTarget->GetExecutableNames(Config);
  }
  return GeneratorTarget->GetLibraryNames(Config);
}

void cmFastbuildTargetGenerator::AddObjectDependencies(
  FastbuildTarget& fastbuildTarget,
  std::vector<std::string>& allObjectDepends) const
{
  auto const FindObjListWhichOutputs = [&fastbuildTarget](
                                         std::string const& output) {
    for (FastbuildObjectListNode const& objList :
         fastbuildTarget.ObjectListNodes) {
      if (objList.ObjectOutputs.find(output) != objList.ObjectOutputs.end()) {
        return objList.Name;
      }
    }
    return std::string{};
  };

  for (FastbuildObjectListNode& objList : fastbuildTarget.ObjectListNodes) {
    objList.PreBuildDependencies.emplace(
      fastbuildTarget.Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX);
    for (auto const& objDep : objList.ObjectDepends) {
      // Check if there is another object list which outputs (OBJECT_OUTPUTS)
      // something that this object list needs (OBJECT_DEPENDS).
      auto anotherObjList = FindObjListWhichOutputs(objDep);
      if (!anotherObjList.empty()) {
        LogMessage("Adding explicit <OBJECT_DEPENDS> dep: " + anotherObjList);
        allObjectDepends.emplace_back(anotherObjList);
        objList.PreBuildDependencies.emplace(std::move(anotherObjList));

      } else {
        LogMessage("Adding <OBJECT_DEPENDS> dep: " + objDep);
        allObjectDepends.emplace_back(objDep);
        objList.PreBuildDependencies.emplace(objDep);
      }
    }
  }
  cmGlobalFastbuildGenerator::TopologicalSort(fastbuildTarget.ObjectListNodes);
}

void cmFastbuildTargetGenerator::AddLinkerNodeDependnecies(
  FastbuildTarget& fastbuildTarget)
{
  for (auto& linkerNode : fastbuildTarget.LinkerNode) {
    linkerNode.PreBuildDependencies.emplace(
      fastbuildTarget.Name + FASTBUILD_DEPS_ARTIFACTS_ALIAS_POSTFIX);

    if (!fastbuildTarget.PreLinkExecNodes.Nodes.empty()) {
      linkerNode.PreBuildDependencies.emplace(
        fastbuildTarget.Name + FASTBUILD_PRE_LINK_ALIAS_POSTFIX);
    }
  }
}

std::string cmFastbuildTargetGenerator::GetClangTidyReplacementsFilePath(
  std::string const& directory, cmSourceFile const& source,
  std::string const& /*config*/) const
{

  std::string objectDir =
    this->ConvertToFastbuildPath(this->GeneratorTarget->GetSupportDirectory());
  std::string const& objectName =
    this->GeneratorTarget->GetObjectName(&source);
  std::string path =
    cmStrCat(directory, '/', objectDir, '/', objectName, ".yaml");
  LogMessage("ClangTidy replacements file: " + path);
  return path;
}

void cmFastbuildTargetGenerator::AddIncludeFlags(std::string& languageFlags,
                                                 std::string const& language,
                                                 std::string const&)
{
  std::vector<std::string> includes;
  this->LocalGenerator->GetIncludeDirectories(includes, this->GeneratorTarget,
                                              language, Config);
  // Add include directory flags.
  std::string includeFlags = this->LocalGenerator->GetIncludeFlags(
    includes, this->GeneratorTarget, language, Config, false);

  this->LocalGenerator->AppendFlags(languageFlags, includeFlags);
}

std::string cmFastbuildTargetGenerator::GetName()
{
  return GeneratorTarget->GetName();
}

std::string cmFastbuildTargetGenerator::ConvertToFastbuildPath(
  std::string const& path) const
{
  return GetGlobalGenerator()->ConvertToFastbuildPath(path);
}

cmGlobalFastbuildGenerator* cmFastbuildTargetGenerator::GetGlobalGenerator()
  const
{
  return this->LocalGenerator->GetGlobalFastbuildGenerator();
}

void cmFastbuildTargetGenerator::AdditionalCleanFiles()
{
  if (cmValue prop_value =
        this->GeneratorTarget->GetProperty("ADDITIONAL_CLEAN_FILES")) {
    auto* lg = this->LocalGenerator;
    cmList cleanFiles(cmGeneratorExpression::Evaluate(*prop_value, lg, Config,
                                                      this->GeneratorTarget));
    std::string const& binaryDir = lg->GetCurrentBinaryDirectory();
    auto* gg = lg->GetGlobalFastbuildGenerator();
    for (auto const& cleanFile : cleanFiles) {
      // Support relative paths
      gg->AddFileToClean(gg->ConvertToFastbuildPath(
        cmSystemTools::CollapseFullPath(cleanFile, binaryDir)));
    }
  }
}
